import { match, MatchFunction } from 'path-to-regexp';
import { exposeFunction, imposedWindow, imposeFunction } from './patch';

export type RouteCallback = () => any;

interface Route {
	pattern: string;
	matcher: MatchFunction;
	onCallbacks: RouteCallback[];
	offCallbacks: RouteCallback[];
}

const registeredRoutes: Route[] = [];

const imposedPushState = imposeFunction(imposedWindow.history.pushState, imposedWindow.history);
imposedWindow.history.pushState = exposeFunction(proxyPushState);

function proxyPushState(data: any, unused: string, url?: string | URL | null): void {
	imposedPushState(data, unused, url);
	onNavigate();
}

addEventListener('popstate', () => {
	onNavigate();
});

function onNavigate() {
	for (const route of registeredRoutes) {
		const cbs = route.matcher(location.pathname) ? route.onCallbacks : route.offCallbacks;
		for (const cb of cbs) cb();
	}
}

function ensureRoute(pattern: string): Route {
	let route = registeredRoutes.find(route => route.pattern === pattern);
	if (route) return route;

	route = {
		pattern,
		matcher: match(pattern),
		onCallbacks: [],
		offCallbacks: []
	};

	registeredRoutes.push(route);
	return route;
}

export function onRoute(pattern: string, cb: RouteCallback, once = false): void {
	const route = ensureRoute(pattern);
	const match = route.matcher(location.pathname);

	route.onCallbacks.push(cb);
	if (match) cb();
}

export function offRoute(pattern: string, cb: RouteCallback, once = false): void {
	const route = ensureRoute(pattern);
	const match = route.matcher(location.pathname);

	route.offCallbacks.push(cb);
	if (!match) cb();
}
